package com.gitee.fsmxm.ocon.core;

import com.gitee.fsmxm.ocon.constant.AppEnum;
import com.gitee.fsmxm.ocon.model.PoolProperties;
import com.gitee.fsmxm.ocon.model.Procedure;
import com.gitee.fsmxm.ocon.model.Task;
import com.gitee.fsmxm.ocon.util.AppUtils;
import com.gitee.fsmxm.ocon.util.ProcedureUtils;
import com.gitee.fsmxm.ocon.util.WinTaskUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author fsmxm
 */
public class ProcessPool implements Pool<Procedure> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessPool.class);

    private PoolProperties poolProperties;

    private String poolName;

    private BlockingQueue<Procedure> idelQueue;

    private BlockingQueue<Procedure> diligentQueue;

    public ProcessPool(PoolProperties poolProperties) {
        poolName = UUID.randomUUID().toString();
        this.poolProperties = poolProperties;
        idelQueue = new ArrayBlockingQueue<>(poolProperties.getInitProcessNum());
        diligentQueue = new ArrayBlockingQueue<>(poolProperties.getInitProcessNum());
    }

    @Override
    public void init() throws ConvertException {
        String appName = poolProperties.getAppName();
        String procedureImage = AppUtils.getImage(appName, poolProperties.getProcedureImage());

        AppUtils.checkAndGetApplication(appName);

        // 是否要先杀进程
        if (poolProperties.isStartAndKillExistProcess()) {
            try {
                final String[] result = WinTaskUtils.forceKillAll(procedureImage);
                if (!result[0].isEmpty()) {
                    LOGGER.info("Kill running process success:\n {}", result[0]);
                }

                if (!result[1].isEmpty()) {
                    LOGGER.info("Kill running process error  :\n {}", result[1]);
                }
            } catch (IOException e) {
                LOGGER.warn("Kill running process [{}] error. ", procedureImage, e);
            }
        }

        // powerpoint 仅支持1个进程
        final String name = appName.substring(0, appName.indexOf('.'));
        final AppEnum appEnum = AppEnum.getInstance(name);
        if (appEnum == AppEnum.POWERPOINT) {
            poolProperties.setInitProcessNum(1);
        }

        // 启动进程，初始化进程池
        for (int i = 0; i < poolProperties.getInitProcessNum(); i++) {
            add();
        }

        LOGGER.info("Init pool success: {}", this.toString());
    }

    @Override
    public String name() {
        return poolName;
    }

    @Override
    public Procedure get() {
        final Procedure procedure = idelQueue.poll();
        if (procedure != null) {
            diligentQueue.offer(procedure);
            procedure.setStartTime(System.currentTimeMillis());
            procedure.setStatus(Procedure.STATUS_BUSY);
        }
        return procedure;
    }

    @Override
    public Procedure get(long time, TimeUnit timeUnit) throws InterruptedException {
        final Procedure procedure = idelQueue.poll(time, timeUnit);
        if (procedure != null) {
            diligentQueue.offer(procedure);
            procedure.setStartTime(System.currentTimeMillis());
            procedure.setStatus(Procedure.STATUS_BUSY);
        }
        return procedure;
    }

    @Override
    public Procedure take() throws InterruptedException {
        final Procedure procedure = idelQueue.take();
        diligentQueue.offer(procedure);
        procedure.setStartTime(System.currentTimeMillis());
        procedure.setStatus(Procedure.STATUS_BUSY);
        return procedure;
    }

    @Override
    public void retrieve(Procedure procedure) {
        if (diligentQueue.remove(procedure)) {
            procedure.setStartTime(-1);
            procedure.setStatus(Procedure.STATUS_IDEL);
            idelQueue.offer(procedure);
        }
    }

    @Override
    public void add() throws UnsupportedAppException {

        String appName = poolProperties.getAppName();
        String procedureImage = AppUtils.getImage(appName, poolProperties.getProcedureImage());

        String application = AppUtils.checkAndGetApplication(appName);

        if (idelQueue.size() + diligentQueue.size() >= poolProperties.getInitProcessNum()) {
            LOGGER.warn("Pool is full: {}", this.toString());
            return;
        }

        // 获取已存在的进程
        List<Integer> excludePidList = new ArrayList<>(4);
        try {
            final List<Task> tasks = WinTaskUtils.listTask(procedureImage);
            excludePidList.addAll(tasks.stream().map(Task::getPid).collect(Collectors.toList()));
        } catch (IOException e) {
            LOGGER.warn("Get process [{}] list error. ", procedureImage, e);
        }

        try {
            final Procedure procedure = ProcedureUtils.create(application, appName);
            // 查询上面创建的procedure进程
            List<Task> tasks = WinTaskUtils.listTask(procedureImage, excludePidList.stream().mapToInt(Integer::intValue).toArray());
            final Task task = tasks.get(0);
            excludePidList.add(task.getPid());

            procedure.setAppName(appName);
            procedure.setComponentPid(task.getPid());
            procedure.setTask(task);
            procedure.setPool(this);
            idelQueue.offer(procedure);
            LOGGER.info("Create procedure: {}", procedure);
        } catch (IOException | ConvertException e) {
            LOGGER.error("Get process [{}] error ", appName, e);
        }
    }

    @Override
    public void add(Procedure procedure) {
        idelQueue.offer(procedure);
    }

    @Override
    public void close() {
        close(idelQueue);
        close(diligentQueue);
    }

    private void close(BlockingQueue<Procedure> queue) {

        while (!queue.isEmpty()) {
            try {
                Procedure procedure = queue.poll(3, TimeUnit.SECONDS);
                if (procedure == null) {
                    continue;
                }
                LOGGER.info("Closing {}", procedure);
                procedure.close();
            } catch (InterruptedException e) {
                LOGGER.warn("Poll from idel queue error. ", e);
            }
        }
    }

    public BlockingQueue<Procedure> getDiligentQueue() {
        return diligentQueue;
    }

    public PoolProperties getPoolProperties() {
        return poolProperties;
    }

    public BlockingQueue<Procedure> getIdelQueue() {
        return idelQueue;
    }

    @Override
    public String toString() {
        return "ProcessPool{" +
                "poolProperties=" + poolProperties +
                ", poolName='" + poolName + '\'' +
                ", idelQueue=" + idelQueue +
                ", diligentQueue=" + diligentQueue +
                '}';
    }
}
